package br.ufpe.cin.imlm2.util.options;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class Option {

	private String name;
	private String alias;
	private String description;
	private Class<?> type;
	private Class<?> componenType;
	private Object defaultValue;
	private Object value;
	private List<Object> allowedValues;
	private Object upperBound;
	private Object lowerBound;

	private static final List<Class<?>> VALID_COMPONENT_TYPES = new ArrayList<Class<?>>();

	static {
		VALID_COMPONENT_TYPES.add(String.class);
		VALID_COMPONENT_TYPES.add(Integer.class);
		VALID_COMPONENT_TYPES.add(Long.class);
		VALID_COMPONENT_TYPES.add(Float.class);
		VALID_COMPONENT_TYPES.add(Double.class);
	}

	public static class OptionBuilder {

		private String name;
		private String alias;
		private String description;
		private Class<?> type;
		private Class<?> componenType;
		private Object defaultValue;
		private List<Object> allowedValues;
		private Object upperBound;
		private Object lowerBound;

		private static final List<Class<?>> VALID_COMPARABLE_TYPES = new ArrayList<Class<?>>();

		static {
			VALID_COMPARABLE_TYPES.add(String.class);
			VALID_COMPARABLE_TYPES.add(Integer.class);
			VALID_COMPARABLE_TYPES.add(Long.class);
			VALID_COMPARABLE_TYPES.add(Float.class);
			VALID_COMPARABLE_TYPES.add(Double.class);
			VALID_COMPARABLE_TYPES.add(Boolean.class);
		}

		public OptionBuilder(String name, Object defaultValue, Class<?> type,
				Class<?> componentType) {
			if (name != null) {
				this.name = name;
				this.alias = name;
			} else
				throw new IllegalArgumentException(
						"An option must have a name.");
			if (defaultValue != null) {
				this.defaultValue = defaultValue;
			}
			this.type = type;
			if (type.equals(List.class)) {
				if (VALID_COMPONENT_TYPES.contains(componentType))
					this.componenType = componentType;
				else
					throw new IllegalArgumentException(
							"Invalid list component type for option "
									+ this.name);
			}
		}

		public OptionBuilder alias(String alias) {
			if (alias != null)
				this.alias = alias;
			return this;
		}

		public OptionBuilder description(String desc) {
			this.description = desc;
			return this;
		}

		public OptionBuilder allowedValues(List<Object> values) {
			if (!this.type.equals(List.class)) {
				if (values.contains(this.defaultValue))
					this.allowedValues = values;
				else
					throw new IllegalArgumentException(
							"Allowed values list doesn't contain default value.");
			} else {
				if (values.containsAll((Collection<?>) this.defaultValue))
					this.allowedValues = values;
				else
					throw new IllegalArgumentException(
							"Some default values are not in the allowed values list.");
			}

			return this;
		}

		@SuppressWarnings("unchecked")
		public OptionBuilder upperBound(Object upper) {
			if (VALID_COMPARABLE_TYPES.contains(this.type))
				if (((Comparable<Object>) upper).compareTo(this.defaultValue) >= 0)
					if (lowerBound == null
							|| (((Comparable<Object>) upper)
									.compareTo(lowerBound) >= 0))
						this.upperBound = upper;
					else
						throw new IllegalArgumentException(
								"Upper bounds can't be bellow lower bounds.");
				else
					throw new IllegalArgumentException(
							"Default value above upper bound.");
			else
				throw new IllegalArgumentException(
						"Type must implement Comparable<T> interface to set upper and lower bounds");
			return this;
		}

		@SuppressWarnings("unchecked")
		public OptionBuilder lowerBound(Object lower) {
			if (VALID_COMPARABLE_TYPES.contains(this.type))
				if (((Comparable<Object>) lower).compareTo(this.defaultValue) <= 0)
					if (upperBound == null
							|| (((Comparable<Object>) upperBound)
									.compareTo(lower) >= 0))
						this.lowerBound = lower;
					else
						throw new IllegalArgumentException(
								"Lower bound can't be above upper bound.");
				else
					throw new IllegalArgumentException(
							"Default value below lower bound.");
			else
				throw new IllegalArgumentException(
						"Type must implement Comparable<T> interface to set upper and lower bounds");
			return this;
		}

		public Option build() {
			return new Option(name, alias, description, type, componenType,
					defaultValue, allowedValues, upperBound, lowerBound);
		}

	}

	private Option(String name, String alias, String description,
			Class<?> type, Class<?> componenType, Object defaultValue,
			List<Object> allowedValues, Object upperBound, Object lowerBound) {
		super();
		this.name = name;
		this.alias = alias;
		this.description = description;
		this.type = type;
		this.componenType = componenType;
		this.defaultValue = defaultValue;
		this.value = defaultValue;
		this.allowedValues = allowedValues;
		this.upperBound = upperBound;
		this.lowerBound = lowerBound;
	}

	public Option(String name, String alias, String description,
			Object defaultVal, Class<?> type, Class<?> componentType) {
		super();
		if (name != null) {
			this.name = name;
		} else
			throw new IllegalArgumentException("An option must have a name.");
		if (alias != null)
			this.alias = alias;
		else
			this.alias = this.name;
		this.description = description;
		if (defaultVal != null) {
			this.defaultValue = defaultVal;
			this.value = defaultVal;
		} else
			throw new IllegalArgumentException(
					"Every option must have a default value");
		if (type != null)
			this.type = type;
		else
			throw new IllegalArgumentException(
					"A type must be defined for an option.");
		if (this.type.equals(List.class)) {
			if (VALID_COMPONENT_TYPES.contains(componentType))
				this.componenType = componentType;
			else
				throw new IllegalArgumentException(
						"Invalid list component type for option " + this.name);
		}

	}

	public String getName() {
		return this.name;
	}

	public String getAlias() {
		return this.alias;
	}

	public void setAlias(String alias) {
		this.alias = alias;
	}

	public String getDescription() {
		return this.description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public Object getValue() {
		return this.value;
	}

	public void setValue(Object value) {
		verifyConstraints(value);
		this.value = value;
	}

	@SuppressWarnings("unchecked")
	private void verifyConstraints(Object value) {
		if (this.allowedValues != null) {
			if (!this.type.equals(List.class)
					&& !this.allowedValues.contains(value))
				throw new IllegalArgumentException("Value " + value.toString()
						+ "  not in allowed values list.");
			else if (this.type.equals(List.class)
					&& !this.allowedValues.containsAll((Collection<?>) value))
				throw new IllegalArgumentException("Some values in list "
						+ value.toString()
						+ " are not in the allowed values list "
						+ this.allowedValues.toString());

		} else if (this.upperBound != null && this.lowerBound != null) {
			if (value instanceof Comparable<?>) {
				// XXX This is probably WRONG.
				Comparable<Object> upper = (Comparable<Object>) this.upperBound;
				Comparable<Object> lower = (Comparable<Object>) this.lowerBound;
				if (upper.compareTo(value) < 0 || lower.compareTo(value) > 0) {
					throw new IllegalArgumentException(
							"Value outside of bounds [" + lower.toString()
									+ " - " + upper.toString() + "]");
				}
			} else
				throw new IllegalArgumentException(
						"Value must implement Comparable<T> interface.");
		} else if (this.upperBound != null) {
			if (value instanceof Comparable<?>) {
				Comparable<Object> upper = (Comparable<Object>) this.upperBound;
				if (upper.compareTo(value) < 0)
					throw new IllegalArgumentException(
							"Value above upper bound [... - "
									+ upper.toString() + "]");
			} else
				throw new IllegalArgumentException(
						"Value must implement Comparable<T> interface.");
		} else if (this.lowerBound != null) {
			if (value instanceof Comparable<?>) {
				Comparable<Object> lower = (Comparable<Object>) this.lowerBound;
				if (lower.compareTo(value) > 0)
					throw new IllegalArgumentException(
							"Value below lower bound [" + lower.toString()
									+ " - ...]");
			} else
				throw new IllegalArgumentException(
						"Value must implement Comparable<T> interface.");
		}
	}

	public Object getDefaultValue() {
		return this.defaultValue;
	}

	public Class<?> getType() {
		return this.type;
	}

	public Class<?> getComponenType() {
		return this.componenType;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof Option) {
			Option op = (Option) obj;
			boolean isEqual = true;
			isEqual = this.getName().equals(op.getName()) && isEqual;
			if (this.getDescription() != null && op.getDescription() != null)
				isEqual = this.getDescription().equals(op.getDescription())
						&& isEqual;
			else
				isEqual = (this.getDescription() == op.getDescription())
						&& isEqual;
			isEqual = this.getType().equals(op.getType()) && isEqual;
			if (this.getComponenType() != null && op.getComponenType() != null)
				isEqual = this.getComponenType().equals(op.getComponenType())
						&& isEqual;
			else
				isEqual = (this.getComponenType() == op.getComponenType())
						&& isEqual;
			isEqual = this.getDefaultValue().equals(op.getDefaultValue())
					&& isEqual;
			return isEqual;
		}
		return false;
	}

	@Override
	public int hashCode() {
		return this.getName().hashCode();
	}
}
